package group6renderer;

import group6renderer.renderernode.*;
import java.util.ArrayList;

/**
 * @author jonnie,Yiannis
 */

public class Parser
{	
    private ErrorLog errors;
    private int cursor;
    private int lineNo=1;
    private String theHTML;
    private TreeNode tree;
    private TreeNode currentNode;
    private ArrayList<String> openTags = new ArrayList<String>();
    private int tagCounter;
    private boolean basicTagsCreated=false;
    
    
    /**
     * Constructor, initialises the string containing the html
     * creates the errorlog object
     * @param the HTML to be parsed
     * 
     */
    public Parser(String theHTML)
    {
    	this.theHTML = theHTML;
    	errors=ErrorLog.errorInstance();
    
    }

    
    /**
     * Gets the next char from the html. If the char is a newline, increase the line counting variable
     * @return the next char
     * @throws java.lang.Exception
     */
    private char getChar() throws Exception
    {
    	if (cursor == theHTML.length())
    	{
	    throw new Exception("EOF");
    	}
    	char curChar = theHTML.charAt(cursor);
    	if(curChar == '\n')			
    			lineNo++;
    		

    return curChar;
    }

    /**
     * Parses in a string of text, up to the first space if incSpaces is true, otherwise until a < or > is seen, indicating the start or end of a tag
     * @param incSpaces whether or not to include spaces
     * @return The parsed string
     */
    private String parseText(boolean incSpaces)
    {
    	StringBuffer theText = new StringBuffer();
    	char theChar ;
    	try
    	{ 

    		theChar = getChar();
    		while (theChar =='<' || theChar == '>')
    		{
    			cursor++;theChar = getChar();
    			errors.addError("Error:Open '<' or closing tag '>' symbols are not in a proper place. Line: "+lineNo+"\r\n");
    		}
		
    		while (cursor<theHTML.length() &&( (theChar!='>' && theChar!='<')))

    		{ if (!incSpaces && Character.isSpaceChar(theChar))
    			break;
    			
    			theText.append(theChar);
    			cursor++;
			theChar = getChar();
    			
    		}
    
    	}
    	catch (Exception ex)
    	{
    	}
    return theText.toString();
    }

    /**
     * Parses a text node, adding it to the tree. Handles the error condition of a lack of header tags
     */
    private void parseTextNode()
    {
    	if (tagCounter==0)
    		{				
    			createBasicTags();
    			errors.addError("Error:No Html tag as the first tag. Line: "+lineNo+"\r\n");
    			errors.addError("Error:Text before the basic tags(HTML,BODY,HEAD).Basic tags were created automatically. Line: "+lineNo+"\r\n");
    			
    		}
    	try
    	{	TextNode text=new TextNode(parseText(true));
    		currentNode.addChild(text);
    	
    	}catch(Exception e)
    	{errors.addError("Error: "+e.getMessage()+"LINE: "+lineNo+"\r\n");
    		
    	}
    
    }

    /**
     * Parses a tag node. Includes handling of closing tags, the src attribute of image tags, and the eventual adding of the node to the tree.
     * Handles error detectio for the cases of out of order tags and other tag errors
     */
    private void parseTag()
    {
    	TreeNode newNode = null;
    	cursor++;
    	try
    	{
    		if (!Character.toString(getChar()).equals("/"))
    		{
		    String tagName=parseText(false);
		    	tagName=tagName.toLowerCase();
		    
		    try
    			{
    				
    				newNode = HTMLConstants.getInstance(tagName);

    				if(newNode instanceof ImageNode)
    					parseImagePath(newNode);	
    				else
    					findTagErrors(tagName);
    				this.checkClosingSymbol(tagName);
    				tagCounter++;
    				
    				if (tagCounter==1)
    					{	
    						if (newNode instanceof HtmlNode)
    							{tree=newNode;addOpenTag(tree);}
    						else
    						{
    							tree= HTMLConstants.getInstance(HTMLConstants.HTML);
    							tree.addChild(newNode);
    							newNode.setParent(tree);
    							addOpenTag(tree);
    							addOpenTag(newNode);
    							
    						}			
    							
    					}
    				else if (newNode instanceof FormatNode && openTags.contains(tagName)) 
    					{
    					errors.addError("Error:There is already an open tag. Line: "+lineNo+" .Tag: "+tagName+"\r\n");	
    					}
    				
    				else
    				{	
    					newNode.setParent(currentNode);
        				currentNode.addChild(newNode);
        				addOpenTag(newNode);
        				
    				}
    			}
    			catch(Exception e) 
    			{	
    				errors.addError("Error: "+e.getMessage()+" LINE: "+lineNo+" .Tag: "+tagName+"\r\n");
					findTagErrors(tagName);
    				this.checkClosingSymbol(tagName);
    				if (currentNode instanceof TitleNode)
    				{
    					if (currentNode.getChildrenNum()!=0)
    					{	openTags.remove(HTMLConstants.TITLE);
    						currentNode=HTMLConstants.currentBasicNode;	
    					}
    				}
    				
    			}
	
    		}
    		else
    		{
    			
    			cursor++;
    			String tagName = parseText(false);
    			tagName=tagName.toLowerCase();
    			if (openTags.contains(tagName))
    				currentNode= closeTag(currentNode,new ArrayList<TreeNode>(),tagName);
    			else if ((basicTagsCreated)&& (tagName.equals(HTMLConstants.HEAD) || tagName.equals(HTMLConstants.BODY) || tagName.equals(HTMLConstants.HTML)))
    			{}	
    			else
    				errors.addError("Error:There is not an equal opening tag- LINE: "+lineNo+" .Tag: "+tagName+"\r\n");
    			
    		}
    	}
    	catch (Exception ex)
    	{
    	}
    	cursor++;
    } 

/**
 * The main tag parsing loop, This goes through the whole html and keeps parsing and adding tags until the end of the document is reached. It then lists the unclosed tags in the error log.
 * @return the root treenode
 */
    public TreeNode parse()
    {
     
    	replaceSymbols();
    	
    	while(cursor < theHTML.length())
     {
    	
    	try
    	{
    		if (Character.toString(getChar()).equals("<"))
    		{
    			parseTag();
    		}
    		else
    		{
    			parseTextNode();
    		}
    	}
    	catch (Exception ex)
    	{
    		System.out.println(ex.toString());
    		return null;
    	}
     }
   
     int j=0;
     while(openTags.size()>0)
     {	j++; 
    	 if (j==1)errors.addError("Errors: The following tags remained unclosed: ");
    	 String tagName=openTags.remove(0);    	 
    	 errors.addError(tagName+", ");
     }
    	 
     return tree;
    }

   
   /**
    * Method to handle the closing of tags and the returning of the currentNode pointer to the correct parent tag where new tags should be added to.
    * Works recursively to handle out of order closing tags
    * @param theNode, the tag to be closed
    * @param unClosedTags, the list of unclosed tags
    * @param tagName, the name of the closing tag
    * @return The new currentNode
    */
   private TreeNode closeTag(TreeNode theNode,ArrayList<TreeNode> unClosedTags,String tagName)
    {	
		TreeNode parent;
    	if (theNode.getName().equalsIgnoreCase(tagName))
    	{
    		TreeNode lastUnclosed;
    		openTags.remove(theNode.getName());
    		if (theNode==tree)
    			return tree;	
    		parent = theNode.parent;
    		while (!unClosedTags.isEmpty())
    		{
    			lastUnclosed = unClosedTags.remove(unClosedTags.size() - 1);
    			try{
    			parent.addChild(lastUnclosed);
    			}
    			catch(Exception e){System.out.print(e.getMessage());}
    			lastUnclosed.setParent(parent);
    			 parent=lastUnclosed;
    		}  		
    	}
    	else
    	{	
    		if (theNode==currentNode)
    			errors.addError("Error:Closing tag in wrong place. LINE: "+lineNo+" .Tag: "+tagName+"\r\n");
    		
    		if (theNode==tree)
    			return tree;
    		
    		TreeNode copyNode=theNode.copy();
    		copyNode.isCopy=true;
    		theNode.copied=true;
    		unClosedTags.add(copyNode);
    		parent = closeTag(theNode.parent,unClosedTags,tagName);
    	}

    return parent;    	
    }

   /**
    * Adds newNode to the list of open tags if it is a branch tag
    * @param newNode the node to be added
    */
   public void addOpenTag(TreeNode newNode)
   {
	   if (!newNode.isLeaf())
	   {currentNode = newNode;openTags.add(currentNode.getName());}
   }

   /**
    * Creates the basic, HTML, HEAD and BODY tags and adds them to the tree
    */
   public void createBasicTags()
   {
	try{
	   tree= HTMLConstants.getInstance(HTMLConstants.HTML);
	   TreeNode node=HTMLConstants.getInstance(HTMLConstants.HEAD);
	   tree.addChild(node);
	   node=HTMLConstants.getInstance(HTMLConstants.BODY);
	   tree.addChild(node);
	   currentNode=node;
	   tagCounter=3;
	   basicTagsCreated=true;
	}catch(Exception e){}
   }

   /**
    * Handles the parsing of the src attribute of the image tag between the ""
    * @param The imagenode to put the path into
    */
   public void parseImagePath(TreeNode node)
   {
	
	   ImageNode img=(ImageNode)node;
	   String imgInfo=parseText(true);
	   int index=imgInfo.indexOf("\"")+1;  
	   String path;
	   int index2=index;
	   while (imgInfo.charAt(index2)!='"')
		  index2++;
	
	   path=imgInfo.substring(index,index2);
	   
	   try{
	   img.setPath(path);
	   }catch(Exception e) {errors.addError("Error: "+e.getMessage()+" LINE: "+lineNo+" .Tag: "+"img"+"\r\n");}

   }

   
   public void findTagErrors(String tagName)
   {
	   
	try{
		
		 boolean errorInTag=false;
		 String errorText="";
		 char theChar=getChar();
		 if (theChar!='<' && theChar!='>')
		 {	 
		 	errorText=parseText(true);errorInTag=true;
		 }
			if (errorInTag)
				errors.addError("Error:The command in the tag,after the tag name is unidentified:"+errorText+". LINE: "+lineNo+" .Tag: "+tagName+"\r\n");
	}catch(Exception e){}
	
  }
   
   
   public void checkClosingSymbol(String tagName)
   {
	   try{
		   if (getChar()=='<')
		   {
			 cursor--;
			errors.addError("Error:Closing a tag with the wrong symbol.'<' instead of '>' LINE: "+lineNo+" .Tag: "+tagName+"\r\n");
		   }
   
   		}catch(Exception e){}
   }
   
   /**
    * Replaces escaped symbols in the html with their proper characters.
    */
   public void replaceSymbols()
   {
   	if (!theHTML.isEmpty())
   	{
   		
   		theHTML=theHTML.replace(HTMLConstants.S_AMP_EXP,HTMLConstants.S_AMP );
   		theHTML=theHTML.replaceAll(HTMLConstants.S_COPY_EXP,HTMLConstants.S_COPY);
   		theHTML=theHTML.replaceAll( HTMLConstants.S_EACUTE_EXP,HTMLConstants.S_EACUTE);
   		theHTML=theHTML.replaceAll(HTMLConstants.S_EGRAVE_EXP,HTMLConstants.S_EGRAVE);
   		theHTML=theHTML.replaceAll(HTMLConstants.S_EUML_EXP,HTMLConstants.S_EUML);
   		theHTML=theHTML.replaceAll(HTMLConstants.S_FRAC12_EXP,HTMLConstants.S_FRAC12);
   		theHTML=theHTML.replaceAll( HTMLConstants.S_NBSP_EXP,HTMLConstants.S_NBSP);
   		theHTML=theHTML.replaceAll(HTMLConstants.S_POUND_EXP,HTMLConstants.S_POUND );
   		theHTML=theHTML.replaceAll(HTMLConstants.S_QUOT_EXP,HTMLConstants.S_QUOT);
   		theHTML=theHTML.replaceAll(HTMLConstants.S_REG_EXP,HTMLConstants.S_REG);
   		theHTML=theHTML.replaceAll(HTMLConstants.S_MICRO_EXP,HTMLConstants.S_MICRO);
   		theHTML=theHTML.replaceAll(HTMLConstants.S_SUP2_EXP,HTMLConstants.S_SUP2);
   		
   	}
   }
  }
